# 静态的 API 调用关系。因为 span.kind=internal 会断，所以先写死一份。
otel_cg_api_parent = {
    # frontend-proxy
    "frontend-proxy,router frontend egress": "client_frontend-proxy,ingress",
    "frontend,POST": "frontend-proxy,router frontend egress",

    # frontend
    "frontend,POST /api/checkout": "frontend,POST",
    "frontend,grpc.oteldemo.CheckoutService/PlaceOrder": "frontend,POST /api/checkout",
    "checkout,oteldemo.CheckoutService/PlaceOrder": "frontend,grpc.oteldemo.CheckoutService/PlaceOrder",
    "frontend,grpc.oteldemo.ProductCatalogService/GetProduct": "frontend,POST /api/checkout",

    "product-catalog,oteldemo.ProductCatalogService/GetProduct": [
        "frontend,grpc.oteldemo.ProductCatalogService/GetProduct",
        "checkout,oteldemo.ProductCatalogService/GetProduct"],

    # checkout
    "checkout,oteldemo.CartService/GetCart": "frontend,grpc.oteldemo.CheckoutService/PlaceOrder",
    "cart,POST /oteldemo.CartService/GetCart": "checkout,oteldemo.CartService/GetCart",

    "checkout,oteldemo.ProductCatalogService/GetProduct": "frontend,grpc.oteldemo.CheckoutService/PlaceOrder",
    # "product-catalog,oteldemo.ProductCatalogService/GetProduct": "checkout,oteldemo.ProductCatalogService/GetProduct",

    "checkout,oteldemo.CurrencyService/Convert": "frontend,grpc.oteldemo.CheckoutService/PlaceOrder",
    "currency,Currency/Convert": "checkout,oteldemo.CurrencyService/Convert",

    "checkout,oteldemo.ShippingService/GetQuote": "frontend,grpc.oteldemo.CheckoutService/PlaceOrder",
    "shipping,oteldemo.ShippingService/GetQuote": "checkout,oteldemo.ShippingService/GetQuote",

    "checkout,oteldemo.PaymentService/Charge": "frontend,grpc.oteldemo.CheckoutService/PlaceOrder",
    "payment,grpc.oteldemo.PaymentService/Charge": "checkout,oteldemo.PaymentService/Charge",

    "checkout,oteldemo.ShippingService/ShipOrder": "frontend,grpc.oteldemo.CheckoutService/PlaceOrder",
    "shipping,oteldemo.ShippingService/ShipOrder": "checkout,oteldemo.ShippingService/ShipOrder",

    "checkout,oteldemo.CartService/EmptyCart": "frontend,grpc.oteldemo.CheckoutService/PlaceOrder",
    "cart,POST /oteldemo.CartService/EmptyCart": "checkout,oteldemo.CartService/EmptyCart",

    "checkout,HTTP POST": "frontend,grpc.oteldemo.CheckoutService/PlaceOrder",
    "email,POST /send_order_confirmation": "checkout,HTTP POST",

    # cart; server_cart aka redis
    "server_cart,HGET": "cart,POST /oteldemo.CartService/GetCart",

    "flagd,flagd.evaluation.v1.Service/ResolveBoolean": "cart,POST",
    "cart,POST": "cart,flagd.evaluation.v1.Service/ResolveBoolean",
    "server_cart,HMSET": "cart,POST /oteldemo.CartService/EmptyCart",
    "server_cart,EXPIRE": "cart,POST /oteldemo.CartService/EmptyCart",

    # shipping
    "shipping,POST": "shipping,oteldemo.ShippingService/GetQuote",
    "quote,POST /getquote": "shipping,POST"
}


# build reversed index
# otel_cg_api_children = {}
# for api, parent in otel_cg_api_parent.items():
#     if parent not in otel_cg_api_children:
#         otel_cg_api_children[parent] = []
#     otel_cg_api_children[parent].append(api)


def lookup_api_cg(span_process_operation):
    hit1 = otel_cg_api_parent.get(span_process_operation, "")
    return hit1
    # 向上跳 1 个 internal span
    # if hit1 == "":
    #     return ""
    # hit2 = otel_cg_api_parent.get(hit1, "")
    # return hit2

    # 向上跳 2 个 internal span


error_api, total_api = 0, 0


# 返回 bool 代表是否满足
def check_api_cg(child_span_process_operation, parent_span_process_operation):
    lookup_span_process_operation = lookup_api_cg(child_span_process_operation)
    global error_api, total_api
    total_api += 1
    # print(f"[deb] total api #{total_api}")
    if type(lookup_span_process_operation) == list:
        if parent_span_process_operation in lookup_span_process_operation:
            return True
        else:
            return False
    if parent_span_process_operation != lookup_span_process_operation:
        error_api += 1
        print(f"[deb] error api #{error_api}")
        print("child_span_process_operation: ", child_span_process_operation)
        print("parent_span_process_operation: ", parent_span_process_operation)
        print("lookup_span_process_operation: ", lookup_span_process_operation)
        return False
    return True


class Span(object):
    def __init__(
            self,
            trace_id,
            sid,
            start_mus,
            duration_mus,
            op_name,
            references,
            process_id,
            span_kind,
            span_tags
    ):
        self.sid = sid  # span_id
        self.trace_id = trace_id
        self.start_mus = start_mus  # 后六位
        self.duration_mus = duration_mus
        self.op_name = op_name
        self.references = references  # 类似于 parent_span_id 作用
        self.process_id = process_id  # 在 coroot 已经完成了 PID 到 Service（ContainerID）的映射。
        self.span_kind = span_kind  # 枚举值 server/client
        self.tags = span_tags
        self.children_spans = []
        self.taken = False
        self.ep = None

    def AddChild(self, child_span_id):
        self.children_spans.append(child_span_id)

    def GetProcessId(self):
        return self.process_id

    # all_processes 相当于一个字典。所以使用 PID更高效。
    def GetProcess(self, all_processes):
        return all_processes[self.trace_id][self.process_id]

    def GetChildProcess(self, all_processes, all_spans):
        assert self.span_kind == "client"
        # print(len(self.children_spans))

        redis_ops = ['HGET', 'HMSET', 'EXPIRE']
        if self.op_name in redis_ops:
            return "redis"

        if len(self.children_spans) == 0:  # edge, leaf
            return "server_" + self.op_name

        assert len(self.children_spans) == 1
        return all_processes[self.trace_id][
            all_spans[self.children_spans[0]].process_id
        ]

    def GetParentProcess(self, all_processes, all_spans):
        # 针对 Root Span，返回的是自身信息。
        if self.IsRoot():  # edge, root
            return "client_" + self.op_name

        assert len(self.references) == 1
        # 采用 "CHILD_OF" 的规范格式。
        # assert self.references['refType'] == "CHILD_OF"
        parent_span_id = self.references[0]
        return all_processes[self.trace_id][all_spans[parent_span_id].process_id]

    def GetId(self):
        return (self.trace_id, self.sid)

    def IsRoot(self):
        return len(self.references) == 0

    def __lt__(self, other):
        # 针对 skip 不可比
        if self.start_mus == "None" or other.start_mus == "None":
            return False
        # fixme 主要是防止 float 与 str 比较，但在 WithSkips 是否正确？
        return self.start_mus < other.start_mus

    def __repr__(self):
        if self.start_mus == "None":
            return "Span:(%s, %s, %s, %s, %s, %s)" % (
                self.trace_id,
                self.sid,
                self.op_name,
                self.start_mus,
                self.duration_mus,
                self.span_kind,
            )
        else:
            return "Span:(%s, %s, %s, %d, %d, %s)" % (
                self.trace_id,
                self.sid,
                self.op_name,
                self.start_mus,
                self.duration_mus,
                self.span_kind,
            )

    def __str__(self):
        return self.__repr__()

    # API 相关的封装

    # 返回 op 其实也就是 API
    def GetOperation(self):
        # op pattern: 存在 space、.、/、_
        return self.op_name

    # 因为 http method 存在重复，所以使用 {Process, Operation} 唯一标识 API。
    def GetProcessOperation(self, all_processes):
        return self.GetProcess(all_processes) + "," + self.GetOperation()

    # 会利用 api_cg check 一遍。
    def GetParentProcessOperation(self, all_processes, all_spans):
        # assert self.span_kind == "server"  # 不需要
        if len(self.references) == 0:
            return "client_" + self.GetProcess(all_processes) + "," + self.op_name
        assert len(self.references) == 1
        parent_span = all_spans[self.references[0]]
        parent_span_process_operation = parent_span.GetProcessOperation(all_processes)
        current_span_process_operation = self.GetProcessOperation(all_processes)
        # if not check_api_cg(current_span_process_operation, parent_span_process_operation):
        #     return None
        return parent_span_process_operation

    # todo 利用 api_cg 中的信息，分离多次重入之间的 span 分区，从根本上实现 tree 结构的 span 分区。
    def GetChildProcessOperation(self, all_processes, all_spans):
        assert self.span_kind == "client"
        if len(self.children_spans) == 0:
            return "server_" + self.GetProcess(all_processes) + "," + self.op_name
        assert len(self.children_spans) == 1
        child_span = all_spans[self.children_spans[0]]
        child_span_process_operation = child_span.GetProcessOperation(all_processes)
        current_span_process_operation = self.GetProcessOperation(all_processes)
        # if not check_api_cg(child_span_process_operation, current_span_process_operation):
        #     return None
        return child_span_process_operation

    # Endpoint 定义
    def GetParentEndpoint(self, all_processes, all_spans):
        return self.GetParentProcess(all_processes, all_spans)
        # return self.GetParentProcessOperation(all_processes, all_spans)
        # return self.GetProcessOperation(all_processes)

    def GetChildEndpoint(self, all_processes, all_spans):
        return self.GetChildProcess(all_processes, all_spans)
        # return self.GetChildProcessOperation(all_processes, all_spans)
        # return self.GetProcessOperation(all_processes)
